const std = @import("std");
const sys = @import("../syscall.zig");
const dprint = @import("../user.zig").fprint;
const c_str = sys.c_str;

// Length modifier
pub const modifiers: std.StaticStringMap(u16) = .initComptime(.{
    .{ "hh", @bitSizeOf(c_char) },
    .{ "h", @bitSizeOf(c_short) },
    .{ "l", @bitSizeOf(c_long) },
    .{ "ll", @bitSizeOf(c_longlong) },
    .{ "z", @bitSizeOf(usize) },
});

const int_specifiers = "duboxX";
const supported_specifiers = int_specifiers ++ "cps%";

fn vdprintf(fd: sys.fd_t, format: c_str, ap: *std.builtin.VaList) callconv(.c) void {
    var state: enum { normal, wait } = .normal;
    const str = std.mem.span(format);

    var i: usize = 0;
    while (i < str.len) : (i += 1) {
        if (state == .normal) {
            if (str[i] == '%') {
                state = .wait;
            } else {
                dprint(fd, "{c}", .{str[i]});
            }
            continue;
        }

        // conversion specifiers
        var end = std.mem.indexOfAny(u8, str[i..], supported_specifiers) orelse 0;
        const bit_count = modifiers.get(str[i .. i + end]) orelse bc: {
            end = 0;
            break :bc @bitSizeOf(c_int);
        };
        i += end;

        switch (str[i]) {
            'c' => dprint(fd, "{c}", .{@cVaArg(ap, c_char)}),
            'p' => dprint(fd, "{p}", .{@cVaArg(ap, *usize)}),
            's' => dprint(fd, "{s}", .{@cVaArg(ap, c_str)}),
            '%' => dprint(fd, "%", .{}),
            else => {
                if (std.mem.indexOfScalar(u8, int_specifiers, str[i]) != null) {
                    switch (bit_count) {
                        inline 8, 16, 32, 64 => |bc| {
                            switch (str[i]) {
                                inline 'd', 'u', 'b', 'o', 'x', 'X' => |char| {
                                    const Int = std.meta.Int(if (char == 'd') .signed else .unsigned, bc);
                                    const fmt = std.fmt.comptimePrint("{{{c}}}", .{if (char == 'u') 'd' else char});
                                    dprint(fd, fmt, .{@cVaArg(ap, Int)});
                                },
                                else => unreachable,
                            }
                        },
                        else => unreachable,
                    }
                } else { // Print unknown % sequence to draw attention.
                    dprint(fd, "%{u}", .{str[i]});
                }
            },
        }
        state = .normal;
    }
}

// NOTE: Uses vdprintf as interim solution until FILE implementation is complete.
pub export fn fprintf(fd: sys.fd_t, format: c_str, ...) void {
    var ap = @cVaStart();
    defer @cVaEnd(&ap);
    vdprintf(fd, format, &ap);
}

pub export fn printf(format: c_str, ...) void {
    var ap = @cVaStart();
    defer @cVaEnd(&ap);
    vdprintf(1, format, &ap);
}
